/* 
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

using System;

using TokenStream = Lucene.Net.Analysis.TokenStream;
using ArrayUtil = Lucene.Net.Util.ArrayUtil;

namespace Lucene.Net.Index
{
    
    /// <summary>  A Payload is metadata that can be stored together with each occurrence 
    /// of a term. This metadata is stored inline in the posting list of the
    /// specific term.  
    /// <p/>
    /// To store payloads in the index a <see cref="TokenStream"/> has to be used that
    /// produces payload data.
    /// <p/>
    /// Use <see cref="TermPositions.PayloadLength"/> and <see cref="TermPositions.GetPayload(byte[], int)"/>
    /// to retrieve the payloads from the index.<br/>
    /// 
    /// </summary>
    [Serializable]
    public class Payload : System.ICloneable
    {
        /// <summary>the byte array containing the payload data </summary>
        protected internal byte[] data;
        
        /// <summary>the offset within the byte array </summary>
        protected internal int internalOffset;
        
        /// <summary>the length of the payload data </summary>
        protected internal int internalLength;
        
        /// <summary>Creates an empty payload and does not allocate a byte array. </summary>
        public Payload()
        {
            // nothing to do
        }
        
        /// <summary> Creates a new payload with the the given array as data.
        /// A reference to the passed-in array is held, i. e. no 
        /// copy is made.
        /// 
        /// </summary>
        /// <param name="data">the data of this payload
        /// </param>
        public Payload(byte[] data):this(data, 0, data.Length)
        {
        }
        
        /// <summary> Creates a new payload with the the given array as data. 
        /// A reference to the passed-in array is held, i. e. no 
        /// copy is made.
        /// 
        /// </summary>
        /// <param name="data">the data of this payload
        /// </param>
        /// <param name="offset">the offset in the data byte array
        /// </param>
        /// <param name="length">the length of the data
        /// </param>
        public Payload(byte[] data, int offset, int length)
        {
            if (offset < 0 || offset + length > data.Length)
            {
                throw new System.ArgumentException();
            }
            this.data = data;
            this.internalOffset = offset;
            this.internalLength = length;
        }

        /// <summary> Sets this payloads data. 
        /// A reference to the passed-in array is held, i. e. no 
        /// copy is made.
        /// </summary>
        public virtual void  SetData(byte[] value, int offset, int length)
        {
            this.data = value;
            this.internalOffset = offset;
            this.internalLength = length;
        }

        /// <summary> Gets or sets a reference to the underlying byte array
        /// that holds this payloads data.  Data is not copied.
        /// </summary>
        public virtual void SetData(byte[] value)
        {
            SetData(value, 0, value.Length);
        }

        /// <summary> Gets or sets a reference to the underlying byte array
        /// that holds this payloads data.  Data is not copied.
        /// </summary>
        public virtual byte[] GetData()
        {
            return this.data;
        }

        /// <summary> Returns the offset in the underlying byte array </summary>
        public virtual int Offset
        {
            get { return this.internalOffset; }
        }

        /// <summary> Returns the length of the payload data. </summary>
        public virtual int Length
        {
            get { return this.internalLength; }
        }

        /// <summary> Returns the byte at the given index.</summary>
        public virtual byte ByteAt(int index)
        {
            if (0 <= index && index < this.internalLength)
            {
                return this.data[this.internalOffset + index];
            }
            throw new System. IndexOutOfRangeException("Index of bound " + index);
        }
        
        /// <summary> Allocates a new byte array, copies the payload data into it and returns it. </summary>
        public virtual byte[] ToByteArray()
        {
            byte[] retArray = new byte[this.internalLength];
            Array.Copy(this.data, this.internalOffset, retArray, 0, this.internalLength);
            return retArray;
        }
        
        /// <summary> Copies the payload data to a byte array.
        /// 
        /// </summary>
        /// <param name="target">the target byte array
        /// </param>
        /// <param name="targetOffset">the offset in the target byte array
        /// </param>
        public virtual void  CopyTo(byte[] target, int targetOffset)
        {
            if (this.internalLength > target.Length + targetOffset)
            {
                throw new System.IndexOutOfRangeException();
            }
            Array.Copy(this.data, this.internalOffset, target, targetOffset, this.internalLength);
        }
        
        /// <summary> Clones this payload by creating a copy of the underlying
        /// byte array.
        /// </summary>
        public virtual System.Object Clone()
        {
            try
            {
                // Start with a shallow copy of data
                Payload clone = (Payload) base.MemberwiseClone();
                // Only copy the part of data that belongs to this Payload
                if (internalOffset == 0 && internalLength == data.Length)
                {
                    // It is the whole thing, so just clone it.
                    clone.data = new byte[data.Length];
                    data.CopyTo(clone.data, 0);
                }
                else
                {
                    // Just get the part
                    clone.data = this.ToByteArray();
                    clone.internalOffset = 0;
                }
                return clone;
            }
            catch (System.Exception e)
            {
                throw new System.SystemException(e.Message, e); // shouldn't happen
            }
        }
        
        public  override bool Equals(System.Object obj)
        {
            if (obj == this)
                return true;
            if (obj is Payload)
            {
                Payload other = (Payload) obj;
                if (internalLength == other.internalLength)
                {
                    for (int i = 0; i < internalLength; i++)
                        if (data[internalOffset + i] != other.data[other.internalOffset + i])
                            return false;
                    return true;
                }
                else
                    return false;
            }
            else
                return false;
        }
        
        public override int GetHashCode()
        {
            return ArrayUtil.HashCode(data, internalOffset, internalOffset + internalLength);
        }
    }
}